﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DBPOIGET.Classes
{
    public class Coordinate
    {
        double clat = 35, clng = 105;
        double _x_pi = Math.PI* 3000.0 / 180.0;

        public Coordinate(double lng,double lat,CoordinateType type = CoordinateType.BD_90)
        {
            _longitude = lng;
            _latitude = lat;
            _type = type;
        }

        private CoordinateType _type;
        /// <summary>
        /// 坐标系类型
        /// </summary>
        public CoordinateType Type
        {
            get { return _type; }
        }

        private double _longitude,_latitude;
        /// <summary>
        /// 经度
        /// </summary>
        public double Longitude
        {
            get { return _longitude; }
        }

        /// <summary>
        /// 纬度
        /// </summary>
        public double Latitude
        {
            get { return _latitude; }
        }

        /// <summary>
        /// 是否在国外
        /// </summary>
        public bool OutOfChina
        {
            get {
                if (this.Longitude < 72.004 || this.Longitude > 137.8347)
                    return true;
                if (this.Latitude < 0.8293 || this.Latitude > 55.8271)
                    return true;
                return false;
            }
        }

        /// <summary>
        /// 转换成WGS84坐标系
        /// </summary>
        public void ToWGS()
        {
            switch (_type)
            {
                case CoordinateType.BD_90:
                    BD2GCJ();
                    GCJ2WGSExact();
                    break;
                case CoordinateType.GCJ_02:
                    GCJ2WGSExact();
                    break;
                case CoordinateType.WGS_84:
                default:
                    break;
            }
            _type = CoordinateType.WGS_84;
        }

        /// <summary>
        /// 转换成火星坐标系
        /// </summary>
        public void ToGCJ()
        {
            switch (Type)
            {
                case CoordinateType.WGS_84:
                    WGS2GCJ();
                    break;
                case CoordinateType.BD_90:
                    BD2GCJ();
                    break;
                case CoordinateType.GCJ_02:
                default:
                    break;
            }
            _type = CoordinateType.GCJ_02;
        }

        /// <summary>
        /// 转换成百度坐标系
        /// </summary>
        public void ToBD()
        {
            switch (Type)
            {
                case CoordinateType.WGS_84:
                    WGS2GCJ();
                    GCJ2BD();
                    break;
                case CoordinateType.GCJ_02:
                    GCJ2BD();
                    break;
                case CoordinateType.BD_90:
                default:
                    break;
            }
            _type = CoordinateType.BD_90;
        }

        private void WGS2GCJ()
        {
            if (!OutOfChina)
            {
                var delta =  calDelta();
                _latitude += delta.Item1;
                _longitude += delta.Item2; 
            }
        }

        private void GCJ2WGS()
        {
            if (!OutOfChina)
            {
                var delta = calDelta();
                _latitude -= delta.Item1;
                _longitude -= delta.Item2;
            }
        }

        private void GCJ2WGSExact()
        {
            var initDelta = 0.01;
            var threshold = 0.000000001;
            double dLat = initDelta, dLon = initDelta;
            double mLat = _latitude - dLat, mLon = _longitude - dLon;
            double pLat = _latitude + dLat, pLon = _longitude + dLon;
            double wgsLat, wgsLon;
            int i = 0;

            while (true)
            {
                wgsLat = (mLat + pLat) / 2;
                wgsLon = (mLon + pLon) / 2;
                Coordinate c = new Coordinate(wgsLon,wgsLat, CoordinateType.WGS_84);
                c.ToGCJ();
                dLat = c.Latitude - _latitude;
                dLon = c.Longitude - _longitude;
                if ((Math.Abs(dLat) < threshold) && (Math.Abs(dLon) < threshold))
                    break;

                if (dLat > 0)
                    pLat = wgsLat;
                else
                    mLat = wgsLat;

                if (dLon > 0)
                    pLon = wgsLon;
                else
                    mLon = wgsLon;

                if (++i > 10000)
                    break;
            }
            _longitude = wgsLon;
            _latitude = wgsLat;
        }


        private void GCJ2BD()
        {
            double x = _longitude, y = _latitude;
            var z = Math.Sqrt(x * x + y * y) + 0.00002 * Math.Sin(y * _x_pi);
            var theta = Math.Atan2(y, x) + 0.000003 * Math.Cos(x * _x_pi);
            _longitude = z * Math.Cos(theta) + 0.0065;
            _latitude = z * Math.Sin(theta) + 0.006;
        }

        private void BD2GCJ()
        {
            double x = _longitude - 0.0065, y = _latitude - 0.006;
            var z = Math.Sqrt(x * x + y * y) - 0.00002 * Math.Sin(y * _x_pi);
            var theta = Math.Atan2(y, x) - 0.000003 * Math.Cos(x * _x_pi);
            _longitude = z * Math.Cos(theta);
            _latitude = z * Math.Sin(theta);
        }

        /// <summary>
        /// 计算偏移量
        /// </summary>
        private Tuple<double,double> calDelta()
        {
            var a = 6378245.0;
            var ee = 0.00669342162296594323;
            var dLat = this.TransformLat(_longitude - clng, _latitude - clat);
            var dLon = this.TransformLng(_longitude - clng, _latitude - clat);
            var radLat = _latitude / 180.0 * Math.PI;
            var magic = Math.Sin(radLat);
            magic = 1 - ee * magic * magic;
            var sqrtMagic = Math.Sqrt(magic);
            var deltalat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * Math.PI);
            var deltalng = (dLon * 180.0) / (a / sqrtMagic * Math.Cos(radLat) * Math.PI);
            Tuple<double, double> delta = new Tuple<double, double>(deltalat, deltalng);
            return delta;
        }

        private double TransformLat(double x,double y)
        {
            var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.Sqrt(Math.Abs(x));
            ret += (20.0 * Math.Sin(6.0 * x * Math.PI) + 20.0 * Math.Sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
            ret += (20.0 * Math.Sin(y * Math.PI) + 40.0 * Math.Sin(y / 3.0 * Math.PI)) * 2.0 / 3.0;
            ret += (160.0 * Math.Sin(y / 12.0 * Math.PI) + 320 * Math.Sin(y * Math.PI / 30.0)) * 2.0 / 3.0;
            return ret;
        }

        private double TransformLng(double x, double y)
        {
            var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.Sqrt(Math.Abs(x));
            ret += (20.0 * Math.Sin(6.0 * x * Math.PI) + 20.0 * Math.Sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
            ret += (20.0 * Math.Sin(x * Math.PI) + 40.0 * Math.Sin(x / 3.0 * Math.PI)) * 2.0 / 3.0;
            ret += (150.0 * Math.Sin(x / 12.0 * Math.PI) + 300.0 * Math.Sin(x / 30.0 * Math.PI)) * 2.0 / 3.0;
            return ret;
        }

    }

    public enum CoordinateType
    {
        WGS_84,
        GCJ_02,
        BD_90
    }
}
